﻿/*******************************************************************************
 * AAEngine
 * Copyright (c) 2010 Mike Jarosch
 *
 * Original source PushButton Engine:
 * Copyright (C) 2009 PushButton Labs, LLC
 * For more information see http://www.pushbuttonengine.com
 *
 * This file is licensed under the terms of the MIT license, which is included
 * in the "MIT License.txt" file at the root directory of this SDK.
 ******************************************************************************/

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
#if WINDOWS_PHONE
using Microsoft.Xna.Framework.Input.Touch;
#endif
using AAEngine.Engine.Core;

namespace AAEngine.Engine
{
    /// <summary>
    /// Helper for reading input from keyboard, gamepad, and touch input. This class 
    /// tracks both the current and previous state of the input devices, and implements 
    /// query methods for high level input actions such as "move up through the menu"
    /// or "pause the game".
    /// </summary>
    public class InputManager : EventDispatcher, ITickedObject
    {
        public const int MaxInputs = 4;

        public readonly KeyboardState[] CurrentKeyboardStates;
        public readonly GamePadState[] CurrentGamePadStates;
        public MouseState CurrentMouseState;

        public readonly KeyboardState[] LastKeyboardStates;
        public readonly GamePadState[] LastGamePadStates;
        public MouseState LastMouseState;

        public readonly bool[] GamePadWasConnected;

#if WINDOWS_PHONE
        public TouchCollection TouchState;

        public readonly List<GestureSample> Gestures = new List<GestureSample>();
#endif

        /// <summary>
        /// Constructs a new input state.
        /// </summary>
        public InputManager()
        {
            CurrentKeyboardStates = new KeyboardState[MaxInputs];
            CurrentGamePadStates = new GamePadState[MaxInputs];

            LastKeyboardStates = new KeyboardState[MaxInputs];
            LastGamePadStates = new GamePadState[MaxInputs];

            GamePadWasConnected = new bool[MaxInputs];

            AAGame.ProcessManager.AddTickedObject(this, Int32.MaxValue);
        }

        /// <summary>
        /// Reads the latest state of the keyboard and gamepad.
        /// </summary>
        public void OnTick(float deltaTime)
        {
            for (int i = 0; i < MaxInputs; i++)
            {
                LastGamePadStates[i] = CurrentGamePadStates[i];
                CurrentGamePadStates[i] = GamePad.GetState((PlayerIndex)i);

                // Keep track of whether a gamepad has ever been
                // connected, so we can detect if it is unplugged.
                if (CurrentGamePadStates[i].IsConnected)
                {
                    GamePadWasConnected[i] = true;
                }

                LastKeyboardStates[i] = CurrentKeyboardStates[i];
                CurrentKeyboardStates[i] = Keyboard.GetState((PlayerIndex)i);
            }

            LastMouseState = CurrentMouseState;
            CurrentMouseState = Mouse.GetState();

#if WINDOWS_PHONE
            TouchState = TouchPanel.GetState();

            Gestures.Clear();
            while (TouchPanel.IsGestureAvailable)
            {
                Gestures.Add(TouchPanel.ReadGesture());
            }
#endif
        }


        /// <summary>
        /// Helper for checking if a key was newly pressed during this update. The
        /// controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When a keypress
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        public bool IsNewKeyPress(Keys key, PlayerIndex? controllingPlayer,
                                            out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return (CurrentKeyboardStates[i].IsKeyDown(key) &&
                        LastKeyboardStates[i].IsKeyUp(key));
            }
            else
            {
                // Accept input from any player.
                return (IsNewKeyPress(key, PlayerIndex.One, out playerIndex) ||
                        IsNewKeyPress(key, PlayerIndex.Two, out playerIndex) ||
                        IsNewKeyPress(key, PlayerIndex.Three, out playerIndex) ||
                        IsNewKeyPress(key, PlayerIndex.Four, out playerIndex));
            }
        }

        public bool IsNewKeyRelease(Keys key, PlayerIndex? controllingPlayer,
                                            out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return (CurrentKeyboardStates[i].IsKeyUp(key) &&
                        LastKeyboardStates[i].IsKeyDown(key));
            }
            else
            {
                // Accept input from any player.
                return (IsNewKeyRelease(key, PlayerIndex.One, out playerIndex) ||
                        IsNewKeyRelease(key, PlayerIndex.Two, out playerIndex) ||
                        IsNewKeyRelease(key, PlayerIndex.Three, out playerIndex) ||
                        IsNewKeyRelease(key, PlayerIndex.Four, out playerIndex));
            }
        }

        /// <summary>
        /// Helper for checking if a key is currently pressed during the update. The
        /// controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When a pressed key
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        public bool IsKeyDown(Keys key, PlayerIndex? controllingPlayer,
                                            out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return (CurrentKeyboardStates[i].IsKeyDown(key));
            }
            else
            {
                // Accept input from any player.
                return (IsKeyDown(key, PlayerIndex.One, out playerIndex) ||
                        IsKeyDown(key, PlayerIndex.Two, out playerIndex) ||
                        IsKeyDown(key, PlayerIndex.Three, out playerIndex) ||
                        IsKeyDown(key, PlayerIndex.Four, out playerIndex));
            }
        }

        public bool IsAnyKeyDown(PlayerIndex? controllingPlayer,
                                            out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return (CurrentKeyboardStates[i].GetPressedKeys().Length > 0);
            }
            else
            {
                // Accept input from any player.
                return (IsAnyKeyDown(PlayerIndex.One, out playerIndex) ||
                        IsAnyKeyDown(PlayerIndex.Two, out playerIndex) ||
                        IsAnyKeyDown(PlayerIndex.Three, out playerIndex) ||
                        IsAnyKeyDown(PlayerIndex.Four, out playerIndex));
            }
        }

        /// <summary>
        /// Helper for checking if a button was newly pressed during this update.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When a button press
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        public bool IsNewButtonPress(Buttons button, PlayerIndex? controllingPlayer,
                                                     out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return (CurrentGamePadStates[i].IsButtonDown(button) &&
                        LastGamePadStates[i].IsButtonUp(button));
            }
            else
            {
                // Accept input from any player.
                return (IsNewButtonPress(button, PlayerIndex.One, out playerIndex) ||
                        IsNewButtonPress(button, PlayerIndex.Two, out playerIndex) ||
                        IsNewButtonPress(button, PlayerIndex.Three, out playerIndex) ||
                        IsNewButtonPress(button, PlayerIndex.Four, out playerIndex));
            }
        }

        /// <summary>
        /// Helper for checking if a button was newly pressed during this update.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When a button press
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        public bool IsNewButtonRelease(Buttons button, PlayerIndex? controllingPlayer,
                                                     out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return (CurrentGamePadStates[i].IsButtonUp(button) &&
                        LastGamePadStates[i].IsButtonDown(button));
            }
            else
            {
                // Accept input from any player.
                return (IsNewButtonRelease(button, PlayerIndex.One, out playerIndex) ||
                        IsNewButtonRelease(button, PlayerIndex.Two, out playerIndex) ||
                        IsNewButtonRelease(button, PlayerIndex.Three, out playerIndex) ||
                        IsNewButtonRelease(button, PlayerIndex.Four, out playerIndex));
            }
        }

        /// <summary>
        /// Helper for checking if a button is currently pressed down.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When a button press
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        public bool IsButtonDown(Buttons button, PlayerIndex? controllingPlayer,
                                                     out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return (CurrentGamePadStates[i].IsButtonDown(button));
            }
            else
            {
                // Accept input from any player.
                return (IsButtonDown(button, PlayerIndex.One, out playerIndex) ||
                        IsButtonDown(button, PlayerIndex.Two, out playerIndex) ||
                        IsButtonDown(button, PlayerIndex.Three, out playerIndex) ||
                        IsButtonDown(button, PlayerIndex.Four, out playerIndex));
            }
        }

        /// <summary>
        /// Helper for getting the state of the left thumbstick.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When thumbstick
        /// movement is detected, playerIndex will report the player involved.
        /// If all thunbsticks are idle and no controllingPlayer is specified,
        /// playerIndex will be four.
        /// </summary>
        public Vector2 LeftThumbstick(PlayerIndex? controllingPlayer,
                                                     out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return CurrentGamePadStates[i].ThumbSticks.Left;
            }
            else
            {
                Vector2 result = LeftThumbstick(PlayerIndex.One, out playerIndex);
                if (result != Vector2.Zero)
                {
                    return result;
                }
                result = LeftThumbstick(PlayerIndex.Two, out playerIndex);
                if (result != Vector2.Zero)
                {
                    return result;
                }
                result = LeftThumbstick(PlayerIndex.Three, out playerIndex);
                if (result != Vector2.Zero)
                {
                    return result;
                }
                result = LeftThumbstick(PlayerIndex.Four, out playerIndex);

                return result;
            }
        }

        /// <summary>
        /// Helper for getting the state of the right thumbstick.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When thumbstick
        /// movement is detected, playerIndex will report the player involved.
        /// If all thunbsticks are idle and no controllingPlayer is specified,
        /// playerIndex will be four.
        /// </summary>
        public Vector2 RightThumbstick(PlayerIndex? controllingPlayer,
                                                     out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return CurrentGamePadStates[i].ThumbSticks.Right;
            }
            else
            {
                Vector2 result = RightThumbstick(PlayerIndex.One, out playerIndex);
                if (result != Vector2.Zero)
                {
                    return result;
                }
                result = RightThumbstick(PlayerIndex.Two, out playerIndex);
                if (result != Vector2.Zero)
                {
                    return result;
                }
                result = RightThumbstick(PlayerIndex.Three, out playerIndex);
                if (result != Vector2.Zero)
                {
                    return result;
                }
                result = RightThumbstick(PlayerIndex.Four, out playerIndex);

                return result;
            }
        }

        /// <summary>
        /// Helper for getting the state of the left trigger.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When trigger
        /// movement is detected, playerIndex will report the player involved.
        /// If all thunbsticks are idle and no controllingPlayer is specified,
        /// playerIndex will be four.
        /// </summary>
        public float LeftTrigger(PlayerIndex? controllingPlayer,
                                                     out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return CurrentGamePadStates[i].Triggers.Left;
            }
            else
            {
                float result = LeftTrigger(PlayerIndex.One, out playerIndex);
                if (result != 0)
                {
                    return result;
                }
                result = LeftTrigger(PlayerIndex.Two, out playerIndex);
                if (result != 0)
                {
                    return result;
                }
                result = LeftTrigger(PlayerIndex.Three, out playerIndex);
                if (result != 0)
                {
                    return result;
                }
                result = LeftTrigger(PlayerIndex.Four, out playerIndex);

                return result;
            }
        }


        /// <summary>
        /// Helper for getting the state of the right trigger.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When trigger
        /// movement is detected, playerIndex will report the player involved.
        /// If all thunbsticks are idle and no controllingPlayer is specified,
        /// playerIndex will be four.
        /// </summary>
        public float RightTrigger(PlayerIndex? controllingPlayer,
                                                     out PlayerIndex playerIndex)
        {
            if (controllingPlayer.HasValue)
            {
                // Read input from the specified player.
                playerIndex = controllingPlayer.Value;

                int i = (int)playerIndex;

                return CurrentGamePadStates[i].Triggers.Right;
            }
            else
            {
                float result = RightTrigger(PlayerIndex.One, out playerIndex);
                if (result != 0)
                {
                    return result;
                }
                result = RightTrigger(PlayerIndex.Two, out playerIndex);
                if (result != 0)
                {
                    return result;
                }
                result = RightTrigger(PlayerIndex.Three, out playerIndex);
                if (result != 0)
                {
                    return result;
                }
                result = RightTrigger(PlayerIndex.Four, out playerIndex);

                return result;
            }
        }

        /// <summary>
        /// Checks for a "menu select" input action.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When the action
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        public bool IsMenuSelect(PlayerIndex? controllingPlayer,
                                 out PlayerIndex playerIndex)
        {
            return IsNewKeyPress(Keys.Space, controllingPlayer, out playerIndex) ||
                   IsNewKeyPress(Keys.Enter, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.A, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.Start, controllingPlayer, out playerIndex);
        }


        /// <summary>
        /// Checks for a "menu cancel" input action.
        /// The controllingPlayer parameter specifies which player to read input for.
        /// If this is null, it will accept input from any player. When the action
        /// is detected, the output playerIndex reports which player pressed it.
        /// </summary>
        public bool IsMenuCancel(PlayerIndex? controllingPlayer,
                                 out PlayerIndex playerIndex)
        {
            return IsNewKeyPress(Keys.Escape, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.B, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.Back, controllingPlayer, out playerIndex);
        }


        /// <summary>
        /// Checks for a "menu up" input action.
        /// The controllingPlayer parameter specifies which player to read
        /// input for. If this is null, it will accept input from any player.
        /// </summary>
        public bool IsMenuUp(PlayerIndex? controllingPlayer)
        {
            PlayerIndex playerIndex;

            return IsNewKeyPress(Keys.Up, controllingPlayer, out playerIndex) ||
                   IsNewKeyPress(Keys.Left, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.DPadLeft, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.LeftThumbstickLeft, controllingPlayer, out playerIndex);
        }


        /// <summary>
        /// Checks for a "menu down" input action.
        /// The controllingPlayer parameter specifies which player to read
        /// input for. If this is null, it will accept input from any player.
        /// </summary>
        public bool IsMenuDown(PlayerIndex? controllingPlayer)
        {
            PlayerIndex playerIndex;

            return IsNewKeyPress(Keys.Down, controllingPlayer, out playerIndex) ||
                   IsNewKeyPress(Keys.Right, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.DPadRight, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.LeftThumbstickRight, controllingPlayer, out playerIndex);
        }


        /// <summary>
        /// Checks for a "pause the game" input action.
        /// The controllingPlayer parameter specifies which player to read
        /// input for. If this is null, it will accept input from any player.
        /// </summary>
        public bool IsPauseGame(PlayerIndex? controllingPlayer)
        {
            PlayerIndex playerIndex;

            return IsNewKeyPress(Keys.Escape, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.Back, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.Start, controllingPlayer, out playerIndex) ||
                   IsNewButtonPress(Buttons.BigButton, controllingPlayer, out playerIndex);
        }

        public bool KeyJustPressed(Keys key)
        {
            PlayerIndex pi;
            return IsNewKeyPress(key, null, out pi);
        }

        public bool KeyJustReleased(Keys key)
        {
            PlayerIndex pi;
            return IsNewKeyRelease(key, null, out pi);
        }

        public bool IsKeyDown(Keys key)
        {
            PlayerIndex pi;
            return IsKeyDown(key, null, out pi);
        }

        public bool IsAnyKeyDown()
        {
            PlayerIndex pi;
            return IsAnyKeyDown(null, out pi);
        }
    }
}
